#include "dds_decoder.h"
#include "file/FileSystem.h"
#include "logger/Logger.h"
#include <string>
#include "image/Image.h"
#include "zlib.h"

namespace easy2d {
	/// when define returns true it means that our architecture uses big endian
	#define CC_HOST_IS_BIG_ENDIAN (bool)(*(unsigned short *)"\0\xff" < 0x100) 
	#define CC_SWAP32(i)  ((i & 0x000000ff) << 24 | (i & 0x0000ff00) << 8 | (i & 0x00ff0000) >> 8 | (i & 0xff000000) >> 24)
	#define CC_SWAP16(i)  ((i & 0x00ff) << 8 | (i &0xff00) >> 8)   
	#define CC_SWAP_INT32_LITTLE_TO_HOST(i) ((CC_HOST_IS_BIG_ENDIAN == true)? CC_SWAP32(i) : (i) )
	#define CC_SWAP_INT16_LITTLE_TO_HOST(i) ((CC_HOST_IS_BIG_ENDIAN == true)? CC_SWAP16(i) : (i) )
	#define CC_SWAP_INT32_BIG_TO_HOST(i)    ((CC_HOST_IS_BIG_ENDIAN == true)? (i) : CC_SWAP32(i) )
	#define CC_SWAP_INT16_BIG_TO_HOST(i)    ((CC_HOST_IS_BIG_ENDIAN == true)? (i):  CC_SWAP16(i) )

	enum {
		CCZ_COMPRESSION_ZLIB,               // zlib format.
		CCZ_COMPRESSION_BZIP2,              // bzip2 format (not supported yet)
		CCZ_COMPRESSION_GZIP,               // gzip format (not supported yet)
		CCZ_COMPRESSION_NONE,               // plain (not supported yet)
	};

	// v2
	enum PVR2TexturePixelFormat : byte
	{
		V2_RGBA4444 = 0x10,
		V2_RGBA5551,
		V2_RGBA8888,
		V2_RGB565,
		V2_RGB555,          // unsupported
		V2_RGB888,
		V2_I8,
		V2_AI88,
		V2_PVRTC2BPP_RGBA,
		V2_PVRTC4BPP_RGBA,
		V2_BGRA8888,
		V2_A8,
	};

	// v3
#define		V3_PVRTC2BPP_RGB  0ULL
#define		V3_PVRTC2BPP_RGBA 1ULL
#define		V3_PVRTC4BPP_RGB  2ULL
#define		V3_PVRTC4BPP_RGBA 3ULL

#define		V3_BGRA8888       0x0808080861726762ULL
#define		V3_RGBA8888       0x0808080861626772ULL
#define		V3_RGBA4444       0x0404040461626772ULL
#define		V3_RGBA5551       0x0105050561626772ULL
#define		V3_RGB565         0x0005060500626772ULL
#define		V3_RGB888         0x0008080800626772ULL
#define		V3_A8             0x0000000800000061ULL
#define		V3_L8             0x000000080000006cULL
#define		V3_LA88           0x000008080000616cULL

	// Values taken from PVRTexture.h from http://www.imgtec.com
	enum PVR2TextureFlag
	{
		Mipmap         = (1<<8),        // has mip map levels
		Twiddle        = (1<<9),        // is twiddled
		Bumpmap        = (1<<10),       // has normals encoded for a bump map
		Tiling         = (1<<11),       // is bordered for tiled pvr
		Cubemap        = (1<<12),       // is a cubemap/skybox
		FalseMipCol    = (1<<13),       // are there false colored MIP levels
		Volume         = (1<<14),       // is this a volume texture
		Alpha          = (1<<15),       // v2.1 is there transparency info in the texture
		VerticalFlip   = (1<<16),       // v2.1 is the texture vertically flipped
	};

	enum PVR3TextureFlag
	{
		PremultipliedAlpha	= (1<<1)	// has premultiplied alpha
	};

	static const char gPVRTexIdentifier[5] = "PVR!";
	static const int PVR_TEXTURE_FLAG_TYPE_MASK = 0xff;

#pragma pack(1)
	struct PVRv2TexHeader
	{
		uint headerLength;
		uint height;
		uint width;
		uint numMipmaps;
		uint flags;
		uint dataLength;
		uint bpp;
		uint bitmaskRed;
		uint bitmaskGreen;
		uint bitmaskBlue;
		uint bitmaskAlpha;
		uint pvrTag;
		uint numSurfs;
	};

	struct PVRv3TexHeader
	{
		uint version;
		uint flags;
		uint64 pixelFormat;
		uint colorSpace;
		uint channelType;
		uint height;
		uint width;
		uint depth;
		uint numberOfSurfaces;
		uint numberOfFaces;
		uint numberOfMipmaps;
		uint metadataLength;
	};

	struct CCZHeader
	{
		unsigned char   sig[4];             // signature. Should be 'CCZ!' 4 bytes
		unsigned short  compression_type;   // should 0
		unsigned short  version;            // should be 2 (although version type==1 is also supported)
		unsigned int    reserved;           // Reserved for users.
		unsigned int    len;                // size of the uncompressed file
	};
#pragma pack()


	bool supportsNPOT = true;
	bool supportsPVRTC = false;
	bool supportsBGRA8888 = true;

	//////////////////////////////////////////////////////////////////////////

	bool isCCZ(const byte *buffer, uint len)
	{
		if (static_cast<size_t>(len) < sizeof(struct CCZHeader))
		{
			return false;
		}

		struct CCZHeader *header = (struct CCZHeader*) buffer;
		return header->sig[0] == 'C' && header->sig[1] == 'C' && header->sig[2] == 'Z' && (header->sig[3] == '!' || header->sig[3] == 'p');
	}

	bool isPvr(const byte* data, uint dataLen)
	{
		if (static_cast<size_t>(dataLen) < sizeof(PVRv2TexHeader) || static_cast<size_t>(dataLen) < sizeof(PVRv3TexHeader))
		{
			return false;
		}

		const PVRv2TexHeader* headerv2 = static_cast<const PVRv2TexHeader*>(static_cast<const void*>(data));
		const PVRv3TexHeader* headerv3 = static_cast<const PVRv3TexHeader*>(static_cast<const void*>(data));

		return memcmp(&headerv2->pvrTag, gPVRTexIdentifier, strlen(gPVRTexIdentifier)) == 0 || CC_SWAP_INT32_BIG_TO_HOST(headerv3->version) == 0x50565203;
	}

	bool testFormatForPvr2TCSupport(PVR2TexturePixelFormat format)
	{
		if (!supportsPVRTC)
		{
			if (format == V2_PVRTC2BPP_RGBA ||
				format == V2_PVRTC4BPP_RGBA)
			{
				return false;
			}
		}

		return true;
	}

	bool testFormatForPvr3TCSupport(uint64 format)
	{
		if (!supportsPVRTC)
		{
			if (format == V3_PVRTC2BPP_RGB  ||
				format == V3_PVRTC2BPP_RGBA ||
				format == V3_PVRTC4BPP_RGB  ||
				format == V3_PVRTC4BPP_RGBA)
			{
				return false;
			}
		}

		return true;
	}

	int uncompressCCZBuffer(const byte* buffer, uint bufferLen, byte*& out)
	{
		struct CCZHeader *header = (struct CCZHeader*) buffer;

		// verify header
		if( header->sig[0] == 'C' && header->sig[1] == 'C' && header->sig[2] == 'Z' && header->sig[3] == '!' )
		{
			// verify header version
			unsigned int version = CC_SWAP_INT16_BIG_TO_HOST( header->version );
			if( version > 2 )
			{
				LOGE("pvr_decoder: Unsupported CCZ header format");
				return -1;
			}

			// verify compression format
			if( CC_SWAP_INT16_BIG_TO_HOST(header->compression_type) != CCZ_COMPRESSION_ZLIB )
			{
				LOGE("pvr_decoder: CCZ Unsupported compression method");
				return -1;
			}
		}
//		else if( header->sig[0] == 'C' && header->sig[1] == 'C' && header->sig[2] == 'Z' && header->sig[3] == 'p' )
//		{
//			// encrypted ccz file
//			header = (struct CCZHeader*) buffer;
//
//			// verify header version
//			unsigned int version = CC_SWAP_INT16_BIG_TO_HOST( header->version );
//			if( version > 0 )
//			{
//				LOGE("pvr_decoder: Unsupported CCZ header format");
//				return -1;
//			}
//
//			// verify compression format
//			if( CC_SWAP_INT16_BIG_TO_HOST(header->compression_type) != CCZ_COMPRESSION_ZLIB )
//			{
//				LOGE("pvr_decoder: CCZ Unsupported compression method");
//				return -1;
//			}
//
//			// decrypt
//			unsigned int* ints = (unsigned int*)(buffer+12);
//			uint enclen = (bufferLen-12)/4;
//
//			decodeEncodedPvr(ints, enclen);
//
//#if _DEBUG
//			// verify checksum in debug mode
//			unsigned int calculated = checksumPvr(ints, enclen);
//			unsigned int required = CC_SWAP_INT32_BIG_TO_HOST( header->reserved );
//
//			if(calculated != required)
//			{
//				LOGE("pvr_decoder: Can't decrypt image file. Is the decryption key valid?");
//				return -1;
//			}
//#endif
//		}
		else
		{
			LOGE("pvr_decoder: Invalid CCZ file");
			return -1;
		}

		unsigned int len = CC_SWAP_INT32_BIG_TO_HOST( header->len );

		out = (byte*)malloc( len );
		if( !out )
		{
			LOGE("pvr_decoder: CCZ: Failed to allocate memory for texture");
			return -1;
		}

		unsigned long destlen = len;
		size_t source = (size_t) buffer + sizeof(*header);
		int ret = uncompress(out, &destlen, (Bytef*)source, bufferLen - sizeof(*header) );

		if( ret != Z_OK )
		{
			LOGE("pvr_decoder: CCZ: Failed to uncompress data");
			free( out );
			out = NULL;
			return -1;
		}

		return len;
	}

	bool initWithPVRv2Data(const byte * data, uint dataLen, ImageInfo& imgInfo)
	{
		int dataLength = 0, dataOffset = 0, dataSize = 0;
		int blockSize = 0, widthBlocks = 0, heightBlocks = 0;
		int width = 0, height = 0;

		//Cast first sizeof(PVRTexHeader) bytes of data stream as PVRTexHeader
		const PVRv2TexHeader *header = static_cast<const PVRv2TexHeader *>(static_cast<const void*>(data));

		//Make sure that tag is in correct formatting
		if (memcmp(&header->pvrTag, gPVRTexIdentifier, strlen(gPVRTexIdentifier)) != 0)
		{
			return false;
		}

		bool _hasPremultipliedAlpha = false;
		unsigned int flags = CC_SWAP_INT32_LITTLE_TO_HOST(header->flags);
		PVR2TexturePixelFormat formatFlags = static_cast<PVR2TexturePixelFormat>(flags & PVR_TEXTURE_FLAG_TYPE_MASK);
		bool flipped = (flags & (unsigned int)VerticalFlip) ? true : false;
		if (flipped)
		{
			LOGE("pvr_decoder: WARNING: Image is flipped. Regenerate it using PVRTexTool");
		}

		if (! supportsNPOT &&
			(static_cast<int>(header->width) != nextPOT(header->width)
			|| static_cast<int>(header->height) != nextPOT(header->height)))
		{
			LOGE("pvr_decoder: ERROR: Loading an NPOT texture (%dx%d) but is not supported on this device", header->width, header->height);
			return false;
		}

		if (!testFormatForPvr2TCSupport(formatFlags))
		{
			LOGE("pvr_decoder: WARNING: Unsupported PVR Pixel Format: 0x%02X. Re-encode it with a OpenGL pixel format variant", (int)formatFlags);
			return false;
		}

		//if (v2_pixel_formathash.find(formatFlags) == v2_pixel_formathash.end())
		//{
		//	LOGE("pvr_decoder: WARNING: Unsupported PVR Pixel Format: 0x%02X. Re-encode it with a OpenGL pixel format variant", (int)formatFlags);
		//	return false;
		//}

		//auto it = Texture2D::getPixelFormatInfoMap().find(v2_pixel_formathash.at(formatFlags));
		//if (it == Texture2D::getPixelFormatInfoMap().end())
		//{
		//	LOGE("pvr_decoder: WARNING: Unsupported PVR Pixel Format: 0x%02X. Re-encode it with a OpenGL pixel format variant", (int)formatFlags);
		//	return false;
		//}

		//_renderFormat = it->first;

		//Reset num of mipmaps
		//_numberOfMipmaps = 0;

		//Get size of mipmap
		imgInfo.width = width = CC_SWAP_INT32_LITTLE_TO_HOST(header->width);
		imgInfo.height = height = CC_SWAP_INT32_LITTLE_TO_HOST(header->height);

		//Get ptr to where data starts..
		dataLength = CC_SWAP_INT32_LITTLE_TO_HOST(header->dataLength);

		//Move by size of header
		imgInfo.dataLen = dataLen - sizeof(PVRv2TexHeader);
		imgInfo.data = static_cast<byte*>(malloc(imgInfo.dataLen * sizeof(byte)));
		memcpy(imgInfo.data, (byte*)data + sizeof(PVRv2TexHeader), imgInfo.dataLen);

		// Calculate the data size for each texture level and respect the minimum number of blocks
		while (dataOffset < dataLength)
		{
			switch (formatFlags) 
			{
			case V2_PVRTC2BPP_RGBA:
				blockSize = 8 * 4; // Pixel by pixel block size for 2bpp
				widthBlocks = width / 8;
				heightBlocks = height / 4;
				break;
			case V2_PVRTC4BPP_RGBA:
				blockSize = 4 * 4; // Pixel by pixel block size for 4bpp
				widthBlocks = width / 4;
				heightBlocks = height / 4;
				break;
			case V2_BGRA8888:
				if (supportsBGRA8888 == false)
				{
					LOGE("pvr_decoder: Image. BGRA8888 not supported on this device");
					return false;
				}
			default:
				blockSize = 1;
				widthBlocks = width;
				heightBlocks = height;
				break;
			}

			// Clamp to minimum number of blocks
			if (widthBlocks < 2)
			{
				widthBlocks = 2;
			}
			if (heightBlocks < 2)
			{
				heightBlocks = 2;
			}

			int bpp = 32;
			dataSize = widthBlocks * heightBlocks * ((blockSize  * bpp) / 8);
			int packetLength = (dataLength - dataOffset);
			packetLength = packetLength > dataSize ? dataSize : packetLength;

			////Make record to the mipmaps array and increment counter
			//_mipmaps[_numberOfMipmaps].address = _data + dataOffset;
			//_mipmaps[_numberOfMipmaps].len = packetLength;
			//_numberOfMipmaps++;

			dataOffset += packetLength;

			//Update width and height to the next lower power of two
			width = max(width >> 1, 1);
			height = max(height >> 1, 1);
		}

		if (formatFlags == V2_RGBA8888) {
			imgInfo.pixelFmt = PF_RGBA8888;
		} else if (formatFlags == V2_RGBA4444) {
			imgInfo.pixelFmt = PF_RGBA4444;
		}

		return true;
	}

	bool initWithPVRv3Data(const byte * data, uint dataLen, ImageInfo& imgInfo)
	{
		if (static_cast<size_t>(dataLen) < sizeof(PVRv3TexHeader))
		{
			return false;
		}

		const PVRv3TexHeader *header = static_cast<const PVRv3TexHeader *>(static_cast<const void*>(data));

		// validate version
		if (CC_SWAP_INT32_BIG_TO_HOST(header->version) != 0x50565203)
		{
			LOGE("pvr_decoder: WARNING: pvr file version mismatch");
			return false;
		}

		// parse pixel format
		uint64 pixelFormat = header->pixelFormat;

		if (!testFormatForPvr3TCSupport(pixelFormat))
		{
			LOGE("pvr_decoder: WARNING: Unsupported PVR Pixel Format: 0x%016llX. Re-encode it with a OpenGL pixel format variant",
				static_cast<unsigned long long>(pixelFormat));
			return false;
		}


		//if (v3_pixel_formathash.find(pixelFormat) == v3_pixel_formathash.end())
		//{
		//	LOGE("pvr_decoder: WARNING: Unsupported PVR Pixel Format: 0x%016llX. Re-encode it with a OpenGL pixel format variant",
		//		static_cast<unsigned long long>(pixelFormat));
		//	return false;
		//}

		//auto it = Texture2D::getPixelFormatInfoMap().find(v3_pixel_formathash.at(pixelFormat));
		//if (it == Texture2D::getPixelFormatInfoMap().end())
		//{
		//	LOGE("pvr_decoder: WARNING: Unsupported PVR Pixel Format: 0x%016llX. Re-encode it with a OpenGL pixel format variant",
		//		static_cast<unsigned long long>(pixelFormat));
		//	return false;
		//}

		//_renderFormat = it->first;

		// flags
		int flags = CC_SWAP_INT32_LITTLE_TO_HOST(header->flags);
		bool _preMulti = false;

		// PVRv3 specifies premultiply alpha in a flag -- should always respect this in PVRv3 files
		if (flags & (unsigned int)PremultipliedAlpha)
		{
			_preMulti = true;
		}

		// sizing
		int width = CC_SWAP_INT32_LITTLE_TO_HOST(header->width);
		int height = CC_SWAP_INT32_LITTLE_TO_HOST(header->height);
		int _width = width;
		int _height = height;
		uint dataOffset = 0, dataSize = 0;
		int blockSize = 0, widthBlocks = 0, heightBlocks = 0;

		uint _dataLen = dataLen - (sizeof(PVRv3TexHeader) + header->metadataLength);
		byte* _data = static_cast<byte*>(malloc(_dataLen * sizeof(byte)));
		memcpy(_data, static_cast<const byte*>(data) + sizeof(PVRv3TexHeader) + header->metadataLength, _dataLen);

		//_numberOfMipmaps = header->numberOfMipmaps;
		//CCAssert(_numberOfMipmaps < MIPMAP_MAX, "Image: Maximum number of mimpaps reached. Increate the CC_MIPMAP_MAX value");
		int _numberOfMipmaps = 1;

		for (int i = 0; i < _numberOfMipmaps; i++)
		{
			switch (pixelFormat)
			{
			case V3_PVRTC2BPP_RGB :
			case V3_PVRTC2BPP_RGBA :
				blockSize = 8 * 4; // Pixel by pixel block size for 2bpp
				widthBlocks = width / 8;
				heightBlocks = height / 4;
				break;
			case V3_PVRTC4BPP_RGB :
			case V3_PVRTC4BPP_RGBA :
				blockSize = 4 * 4; // Pixel by pixel block size for 4bpp
				widthBlocks = width / 4;
				heightBlocks = height / 4;
				break;
			case V3_BGRA8888:
				if( !supportsBGRA8888 )
				{
					LOGE("pvr_decoder: Image. BGRA8888 not supported on this device");
					return false;
				}
			default:
				blockSize = 1;
				widthBlocks = width;
				heightBlocks = height;
				break;
			}

			// Clamp to minimum number of blocks
			if (widthBlocks < 2)
			{
				widthBlocks = 2;
			}
			if (heightBlocks < 2)
			{
				heightBlocks = 2;
			}

			int bpp = 32;
			dataSize = widthBlocks * heightBlocks * ((blockSize  * bpp) / 8);
			uint packetLength = _dataLen - dataOffset;
			packetLength = packetLength > dataSize ? dataSize : packetLength;

			//_mipmaps[i].address = _data + dataOffset;
			//_mipmaps[i].len = static_cast<int>(packetLength);

			dataOffset += packetLength;
			if (dataOffset > _dataLen) {
				LOGE("TexurePVR: Invalid lenght");
			}
			//assert(dataOffset <= _dataLen, "CCTexurePVR: Invalid lenght");


			width = max(width >> 1, 1);
			height = max(height >> 1, 1);
		}

		return true;
	}

	//////////////////////////////////////////////////////////////////////////

	bool load_pvr_from_memory(const void* buffer, uint bufferLen, ImageInfo& imgInfo)
	{
		bool success = false;
		bool isCCZBuff = false;
		byte* unpackedData = (byte*)buffer;
		uint unpackedLen = bufferLen;

		if ( isCCZ((const byte*)buffer, bufferLen) ) {
			unpackedLen = uncompressCCZBuffer((const byte*)buffer, bufferLen, unpackedData);
			isCCZBuff = true;
		}

		if ( !isPvr(unpackedData, unpackedLen) )
			return false;

		do 
		{
			success = initWithPVRv2Data(unpackedData, unpackedLen, imgInfo);
			if (success)
				break;

			success = initWithPVRv3Data(unpackedData, unpackedLen, imgInfo);
			if (success)
				break;

		} while (0);


		if (isCCZBuff) {
			free(unpackedData);
		}
	
		return success;
	}

	

}